#include "Windows.h"
#include "HandTrack.h"
#include <iostream>
#include <ctime>
#include <fstream>
#include <math.h>
#include <dirent.h>

#define RED     CV_RGB(255, 0, 0)
#define GREEN   CV_RGB(0, 255, 0)
#define BLUE    CV_RGB(0, 0, 255)
#define YELLOW  CV_RGB(255, 255, 0)
#define PURPLE  CV_RGB(255, 0, 255)
#define GREY    CV_RGB(200, 200, 200)
#define CONTOUR_MODE CV_RETR_EXTERNAL
#define CONTOUR_METHOD CV_CHAIN_APPROX_SIMPLE
int findBiggestContour(const vector<vector<Point>>& mContours);
void LogData(Mat frame, string text);
void saveFrame(Mat& frame);
HandTrack::HandTrack(void)
{
	fontFace = FONT_HERSHEY_PLAIN;
	min_area = 5000.0;
	max_area = 25.000;

	MIN_FINGER_DEPTH = 20;
	MAX_FINGER_ANGLE = 60;
	MIN_THUMB = 130;
	MAX_THUMB = 200;
	isCodeBookInitialized = false;

	nrFrames = 0;
	nrFramesToLearnBG = 200;

	nrSamples = 7;
	nameWindows();
	initVideoCapture();
}

HandTrack::~HandTrack(void)
{

}


void HandTrack::nameWindows()
{
	window_output = "output";
	window_mask = "window_mask";
	window_foreground = "window_foreground";
}

void HandTrack::init_windows()
{
	cvNamedWindow(window_output, CV_WINDOW_AUTOSIZE);

	cvNamedWindow(window_mask, CV_WINDOW_KEEPRATIO);

	cvNamedWindow(window_foreground, CV_WINDOW_KEEPRATIO);
}

void HandTrack::initVideoCapture()
{
	videoCap = VideoCapture(1);
	int retries = 20;
	while(!videoCap.isOpened() && retries > 0)
	{
		fprintf(stderr, "Error initializing the camera.retry. \n");
		videoCap = VideoCapture(1);
		retries--;
		if(retries == 0)
		{
			printf("capture is null. exit");
			return;
		}
	}
}

bool HandTrack::videoFrameCapture()
{
	if(!videoCap.isOpened())
	{
		initVideoCapture();
	}
	int retry = 20;
	videoCap >> frame;
	while(!frame.data && retry > 0)
	{
		printf("frame is null.retry.\n");
		videoCap >> frame;
		retry--;
		if(retry==0)
		{
			printf("capture is null. exit");
			return false;
		}
	}
	return true;

}

void HandTrack::initContourAndHullVectors()
{
	hullI = vector<vector<int>>(contours.size());
	hullP = vector<vector<Point>>(contours.size());
	defects = vector<vector<Vec4i>>(contours.size());
}

int findBiggestContour(const vector<vector<Point>>& mContours)
{
	int indexOfContour = -1;
	int sizeOfBiggestContour = 0;
	for(int k = 0; k < mContours.size();k++)
	{
		if(mContours[k].size() > sizeOfBiggestContour)
		{
			sizeOfBiggestContour = mContours[k].size();
			indexOfContour = k;
		}
	}
	return indexOfContour;
}

int HandTrack::findBiggestContour()
{
	int indexOfContour = -1;
	int sizeOfBiggestContour = 0;
	for(int k = 0; k < contours.size();k++)
	{
		if(contours[k].size() > sizeOfBiggestContour)
		{
			sizeOfBiggestContour = contours[k].size();
			indexOfContour = k;
		}
	}
	return indexOfContour;
}

void HandTrack::update()
{
	downSampleFrame();
	pyrDown(mask, mask);
	Mat local_thr_frame;
	mask.copyTo(local_thr_frame);

	findContours(local_thr_frame, contours, CONTOUR_MODE, CONTOUR_METHOD);
	if(contours.size() == 0)
	{
		return;
	}
	initContourAndHullVectors();
	int index = findBiggestContour();
	if(index!=-1)
	{
		bRect = boundingRect(contours[index]);
		convexHull(Mat(contours[index]), hullP[index],false, true);
		convexHull(Mat(contours[index]), hullI[index], false, false);
		approxPolyDP(Mat(hullP[index]), hullP[index], 18, true);
		if(contours[index].size() > 3)
		{
			convexityDefects(contours[index], hullI[index], defects[index]);
			filterDefects(index);
			extractContourInfo(index);
		}
		getFingerTips(index);
		bool isHand = detectIsHandContour();
		if(isHand)
		{
			drawHandContours(index);
			findFingers();
			drawFingerTips();
		}
	}
	pyrUp(mask, mask);
	upSampleFrame();
}


void HandTrack::getFingerTips(int index)
{
	fingerTips.clear();
	int k = 0;

	vector<Vec4i>::iterator itr = defects[index].begin();
	while(itr!=defects[index].end())
	{
		Vec4i& vec = (*itr);

		int startIdx = vec[0];
		Point ptStart(contours[index][startIdx]);

		int endidx = vec[1];
		Point ptEnd(contours[index][endidx]);

		int faridx = vec[2];
		Point ptFar(contours[index][faridx]);

		if(k == 0)
		{
			fingerTips.push_back(ptStart);
			k++;
		}
		if(fingerTips.size() < 5)
		{
			fingerTips.push_back(ptEnd);
			itr++;
			k++;
		}
		else
		{
			break;
		}

	}
	LogData(frame,"Nr of fingers: " + fingerTips.size());
	if(fingerTips.size()==0)
	{
		//checkForOneFinger(index);
	}
}
void HandTrack::checkForOneFinger(int index)
{
	vector<Point>::iterator itr = contours[index].begin();
	while(itr != contours[index].end())
	{
		//TODO intelligent algorithm to track fingers
		//in different positions
		//Currently it only works if the hand is up.
		Point ptr = (*itr);
		//Point pt = (*itr);
		break;
	}
}

void HandTrack::drawFingerTips(){
	Point p;
	int k=0;
	for(int i=0;i<fingerTips.size();i++){
		p = fingerTips[i];
		if(namedFingers[i]==FingerName::THUMB)
		{
			putText(frame, "Thumb", p-Point(0,30),fontFace, 1.2f,Scalar(200,200,200),2);
		}
		char* ival = _itoa(i, new char(10), 10);
		putText(frame, ival, p-Point(0,10),fontFace, 1.2f,Scalar(200,200,200),2);
		circle(frame, p, 5, Scalar(100,255,100), 4 );
	}
	char* nr = itoa(fingerTips.size(), new char(10), 10);
	putText(frame, nr, Point(20,20), fontFace, 1.2f,Scalar(200,200,200),2);
}

void HandTrack::drawHandContours(int contourIdx)
{
	drawContours(frame, hullP, contourIdx, Scalar(255, 255, 0), 2, 8, vector<Vec4i>(), 0, Point());
	rectangle(frame, bRect.tl(), bRect.br(), Scalar(200,200,200));
}

bool HandTrack::detectIsHandContour()
{
	double bHeight = bRect.height;
	double bWidth = bRect.width;

	bool isHand = true;
	if(bHeight==0 || bWidth==0)
	{
		isHand = false;
	}
	else if(fingerTips.size() > 5)
	{
		isHand = false;
	}
	else if(bRect.width  < 5 || bRect.height < 5)
	{
		isHand = false;
	}
	else if(bHeight/bWidth > 4 || bWidth/bHeight > 4)
	{
		isHand = false;
	}
	if(isHand)
	{
		circle(frame, bRect.tl(), 3, Scalar(20,20,20),3);
		circle(frame, bRect.br(), 3, Scalar(20,20,20),3);
	}
	return isHand;
}


void HandTrack::filterDefects(int index)
{
	double startEndMinTolerance = bRect.height/8;
	double startEndMaxTolerance = bRect.height;
	double startFarTolerance = bRect.height/5;

	float angleStartFarEnd = 100;
	float angleFarStartEnd = 95;
	float angleZeroDegree = 0;
	vector<Vec4i> newDefects;
	int startIdx, endIdx, farIdx;
	vector<Vec4i>::iterator it = defects[index].begin();
	while(it!=defects[index].end())
	{
		Vec4i& v = (*it);
		startIdx = v[0];
		Point ptStart(contours[index][startIdx]);

		endIdx = v[1];
		Point ptEnd(contours[index][endIdx]);

		farIdx = v[2];
		Point ptFar(contours[index][farIdx]);
		float endFarTolerance = startFarTolerance;
		bool isStartEndMinDistanceSatisfied = getDistanceBetweenPoints(ptStart, ptEnd) > startEndMinTolerance;
		bool isStartEndMaxDistanceSatisfied = getDistanceBetweenPoints(ptStart, ptEnd) < startEndMaxTolerance;
		bool isStartFarDistanceSatisfied = getDistanceBetweenPoints(ptStart, ptFar) > startFarTolerance;
		bool isEndFarDistanceSatisfied = getDistanceBetweenPoints(ptEnd, ptFar) > endFarTolerance;
		bool isAngleStartFarEndSatisfied = getAngle(ptStart, ptFar, ptEnd) < angleStartFarEnd;

		if(isStartFarDistanceSatisfied &&
			isEndFarDistanceSatisfied &&
			isStartEndMaxDistanceSatisfied &&
			isStartEndMinDistanceSatisfied &&
			isAngleStartFarEndSatisfied)
		{
			Point ptMiddle;
			ptMiddle.x = (ptStart.x + ptEnd.x) / 2;
			ptMiddle.y = (ptStart.y + ptEnd.y) / 2;
			int depthDistance = getDistanceBetweenPoints(ptMiddle, ptFar);
			int angle = angleToMajor(ptMiddle);
			int angleEnd = angleToMajor(ptEnd);
			if(depthDistance > bRect.height/15 && depthDistance < bRect.height/2.2)
			{
				newDefects.push_back(v);
			}
		}
		it++;
	}

	nrOfDefects = newDefects.size();
	defects[index].swap(newDefects);
	removeRedundantEndPoints(defects[index], index);

}

/**
Trying to remove redundat points like when the end point is the start point
and the other way round.
*/
void HandTrack::removeRedundantEndPoints(vector<Vec4i> newDefects, int index)
{
	Vec4i temp;
	float tolerance = bRect.height/10;
	int startIdx, endidx, farIdx;
	int startidx2, endidx2;
	for(int i = 0; i < newDefects.size(); i++)
	{
		for(int j = i; j < newDefects.size(); j++)
		{
			bool isBreak = false;
			startIdx = newDefects[i][0];
			Point ptStart(contours[index][startIdx]);

			endidx = newDefects[i][1];
			Point ptEnd(contours[index][endidx]);

			startidx2 = newDefects[j][0];
			Point ptStart2(contours[index][startidx2]);

			endidx2 = newDefects[j][1];
			Point ptEnd2(contours[index][endidx2]);

			if(getDistanceBetweenPoints(ptStart, ptEnd2) < tolerance)
			{
				contours[index][startIdx] = ptEnd2;
				break;
			}

			if(getDistanceBetweenPoints(ptEnd, ptStart2) < tolerance)
			{
				contours[index][startidx2] = ptEnd;
			}
		}
	}
}


/**
We calculate the angle that is between the line AB and BC which is
the angle ABC.
After, we calculate the distance betwen Point A and Point B, Point B and Point C, the dot product between vector AB and vector BC and at the end we calculate the angle
between AB and BC by using the formula cos(omega) = ABdotBC/|AB||BC|.
*/
float HandTrack::getAngle(Point a, Point b, Point c)
{
	float lineAB = getDistanceBetweenPoints(a, b);
	float lineBC = getDistanceBetweenPoints(b, c);
	float dotABBCProduct = (a.x - b.x)*(c.x - b.x) + (a.y - b.y)*(c.y - b.y);
	float omegaAngle = (dotABBCProduct / (lineAB * lineBC));
	float angle = acos(omegaAngle);
	float mAngle = angle * 180/PI;
	return mAngle;
}
/**
Distance between Point A and Point B in module.
*/
float HandTrack::getDistanceBetweenPoints(Point a, Point b)
{
	double xDistance = pow((double)(a.x - b.x), 2);
	double yDistance = pow((double)(a.y - b.y), 2);
	float distanceAB = sqrt(fabs(xDistance + yDistance));
	return distanceAB;
}
void HandTrack::initMyCodeBookModel()
{
	cbModel = createCodeBookModel();
	for(int i = 0; i <  imgLen;i++)
	{
		cbModel[i].numEntries = 0;
	}
	for(int j = 0; j < CHANNELS; j++)
	{
		channelsActive[j] = true;
	}

}

codeBook* HandTrack::createCodeBookModel()
{
	imgLen = frame.cols * frame.rows;
	codeBook* cbModel = new codeBook[imgLen];
	cbModel->modMin[0] = 12;
	cbModel->modMin[1] = cbModel->modMin[2] = 12;
	cbModel->modMax[0] =18;
	cbModel->modMax[1] = cbModel->modMax[2] = 10;
	cbModel->cbBounds[0] = cbModel->cbBounds[1] = cbModel->cbBounds[2] = 10;
	return cbModel;
}

void HandTrack::updateModel()
{
	nrFrames++;
	uchar* pixel;
	if(nrFrames==1)
	{
		initMyCodeBookModel();
		initMyCodeBookMaskImages();
	}
	cvtColor(frame, yCrCbImg, CV_BGR2YCrCb);

	if(nrFrames - 1 < nrFramesToLearnBG)
	{
		pixel = yCrCbImg.data;
		for(int k = 0; k < imgLen;k++)
		{
			updateCodeBook(pixel, cbModel, k, CHANNELS);
			pixel +=3;
		}
	}
	if(nrFrames-1 == nrFramesToLearnBG)
	{
		for(int k = 0; k < imgLen; k++)
		{
			clearStaleEntries(cbModel[k]);
		}
	}
	if(nrFrames-1 > nrFramesToLearnBG)
	{
		if(!isCodeBookInitialized)
		{
			isCodeBookInitialized = true;
		}

		pixel = yCrCbImg.data;
		uchar* maskPixel  = iMaskCodeBook.data;
		uchar maskPixelCodebook;
		int counter = 0;
		for(int k = 0; k < imgLen;k++)
		{
			maskPixelCodebook = computeBackgroundDiff(pixel, cbModel, k, CHANNELS);
			*maskPixel++ = maskPixelCodebook;
			pixel += 3;
		}
		iMaskCodeBook.copyTo(iMaskCodeBookCC);
		processMask(iMaskCodeBookCC);
		iMaskCodeBookCC.copyTo(mask);
	}
}
/**
@param c. We update the codebook model by either increasing the modMin and modMax values - the classification bounds or 
by reducing the number of channels active from the color model YCrCb, e.g 1,0,0 means only the Y, brightness axis is active.
*/
void HandTrack::updateCodeBookValues(char c)
{
	switch(c)
	{
	case '1':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 0;
		channelsActive[1] = 1;
		channelsActive[2] = 0;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '2':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 0;
		channelsActive[1] = 0;
		channelsActive[2] = 1;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '3':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 1;
		channelsActive[1] = 1;
		channelsActive[2] = 1;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '4':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 0;
		channelsActive[1] = 1;
		channelsActive[2] = 1;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '5':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 1;
		channelsActive[1] = 0;
		channelsActive[2] = 0;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '6':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 0;
		channelsActive[1] = 1;
		channelsActive[2] = 1;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case '7':
		printf("CodeBook YCrCb Channels active: ");
		channelsActive[0] = 1;
		channelsActive[1] = 0;
		channelsActive[2] = 1;
		for(int k = 0; k < 3;k++)
		{
			printf("%d", channelsActive[k]);
		}
		printf("\n");
		break;
	case 'i':
		for(int ii = 0; ii < CHANNELS; ii++)
		{
			if(channelsActive[ii])
			{
				cbModel->modMax[ii] += 1;
				printf("%.4d,",cbModel->modMax[ii]);
			}
		}
		printf(" CodeBook High Side increase.\n");
		break;
	case 'k':
		for(int ii = 0; ii < CHANNELS;ii++)
		{
			if(channelsActive[ii])
			{
				cbModel->modMax[ii] -= 1;
				printf("%.4d,",cbModel->modMax[ii]);
			}
		}
		printf(" CodeBook High Side decrease.\n");
		break;
	case 'o':
		for(int ii = 0; ii < CHANNELS;ii++)
		{
			if(channelsActive[ii])
			{
				cbModel->modMin[ii] += 1;
				printf("%.4d,",cbModel->modMin[ii]);
			}
		}
		printf(" CodeBook Low Side increase.\n");
		break;
	case 'l':
		for(int ii = 0; ii < CHANNELS;ii++)
		{
			if(channelsActive[ii])
			{
				cbModel->modMin[ii] -= 1;
				printf("%.4d,",cbModel->modMin[ii]);
			}
		}
		printf(" CodeBook Low Side decrease.\n");
		break;
	case 'c':
		for(int k = 0; k < imgLen;k++)
		{
			clearStaleEntries(cbModel[k]);
		}
		break;

	case 'h':
		printf("\n Backround subtraction using codebook\n"
			"=== CODEBOOK PARAMS ===\n"
			"\t -  to adjust channel 0(y) or 1(Cr) or 2(Cb) respectively\n"
			"\t1	- adjust all 3 channels at once\n"
			"\t2	- adjust the third channel\n"
			"\ti,o	- bump upper threshold up,down by 1\n"
			"\tk,l	- bump lower threshold up,down by 1\n"
			);
		break;

	case 'u':
		cvtColor(frame, yCrCbImg, CV_BGR2YCrCb);
		uchar* pixel = 0;
		for(int k = 0; k < 10;k++)
		{
			pixel = yCrCbImg.data;
			for(int k = 0; k < imgLen;k++)
			{
				updateCodeBook(pixel, cbModel, k, CHANNELS);
				pixel +=3;
			}
		}
		break;
	}
}


void HandTrack::initMyCodeBookMaskImages()
{
	iMaskCodeBook =  Mat(frame.rows, frame.cols, CV_8U);
	iMaskCodeBookCC =  Mat(frame.rows, frame.cols, CV_8U);
	iMaskCodeBook.setTo(Scalar(255));
}



void HandTrack::upSampleFrame()
{
	pyrUp(frame, frame);
}

void HandTrack::downSampleFrame()
{
	pyrDown(frame, frame);
}






int HandTrack::computeAngleBetween(Point tip, Point prev, Point next)
{

	return abs((int)cvRound(convertToDegrees(atan2((double)(next.x - tip.x),(double)(next.y - tip.y)) -
		atan2((double)(prev.x - tip.x), (double)(prev.y - tip.y)))));
}



void HandTrack::extractContourInfo(int index)
{
	vector<Point> contour = contours[index];
	Moments mu = moments(contour, false);
	if(mu.m00)
	{
		Point2f mc = Point2f(mu.m10/mu.m00, mu.m01/mu.m00);
		cogPt.x = mc.x;
		cogPt.y = mc.y;	
	}
	double m11 = mu.mu11;
	double m20 = mu.mu20;
	double m02 = mu.mu02;
	contourAxisAngle = calculateTilt(m11, m20, m02);
}

int HandTrack::calculateTilt(double m11, double m20, double m02)
{
	double diff = m20 - m02;
	if (diff == 0) {
		if (m11 == 0)
			return 0;
		else if (m11 > 0)
			return 45;
		else   // m11 < 0
			return -45;
	}

	double theta = 0.5 * atan2(2*m11, diff);
	int tilt = (int) cvRound(convertToDegrees(theta));

	if ((diff > 0) && (m11 == 0))
		return 0;
	else if ((diff < 0) && (m11 == 0))
		return -90;
	else if ((diff > 0) && (m11 > 0))  // 0 to 45 degrees
		return tilt;
	else if ((diff > 0) && (m11 < 0))  // -45 to 0
		return (180 + tilt);   // change to counter-clockwise angle
	else if ((diff < 0) && (m11 > 0))   // 45 to 90
		return tilt;
	else if ((diff < 0) && (m11 < 0))   // -90 to -45
		return (180 + tilt);  // change to counter-clockwise angle

	printf("Error in moments for tilt angle.\n");
	return 0;
}

double HandTrack::convertToDegrees(float theta)
{
	double degrees;
	degrees = theta * 180/ M_PI;
	return degrees;
}

int HandTrack::angleToMajor(Point fingerTip)
{
	double yOffset = (double)(cogPt.y - fingerTip.y);  // make y positive up screen
	double xOffset = (double)(fingerTip.x - cogPt.x);
	double theta = atan2(yOffset, xOffset);
	int angleTip = (int)cvRound( convertToDegrees(theta));
	//line(frame,fingerTip, cogPt, Scalar(100,100,100));
	circle(frame, cogPt, 5, Scalar(255, 255, 255));
	return angleTip + abs(90 - contourAxisAngle);
}

void HandTrack::findFingers()
{
	if(fingerTips.size() == 0)
	{
		return;
	}
	int val = FingerName::UNKNOWN;
	namedFingers.clear();
	for (int i=0; i < fingerTips.size(); i++)
	{
		namedFingers.insert(make_pair(i, FingerName::UNKNOWN));
	}
	labelThumbIndex();
	labelUnknowns();
}

void HandTrack::labelThumbIndex()
{
	bool foundThumb = false;
	bool foundIndex = false;
	int i = fingerTips.size()-1;
	while ((i >= 0)) {
		int angle = angleToMajor(fingerTips[i]);
		if ((angle <= MAX_THUMB) && (angle>MIN_THUMB) && !foundThumb) {
			if(namedFingers.at(i)!=NULL)
			{
				cout<<"Erasing element at i index"<<namedFingers.at(i)<<endl;
				namedFingers.erase(i);
			}
			namedFingers.insert(make_pair(i, FingerName::THUMB));
			cout<<"Found thumb."<<endl;
			foundThumb = true;
		}
		i--;
	}
}

void HandTrack::labelUnknowns()
{
	// find first named finger
	int i = 0;
	while ((i < namedFingers.size()) && (namedFingers.at(i) == FingerName::UNKNOWN))
		i++;
	if (i == namedFingers.size())   // no named fingers found, so give up
		return;

	int index = namedFingers.at(i);
	getPrevious(index);
	getNext(index);
	//labelPrev(nms, i, name);    // fill-in backwards
	//labelFwd(nms, i, name);     // fill-in forwards
}


void HandTrack::draw_fingers()
{
	if(fingerTips.size() == 0)
	{
		return;
	}
	circle(frame, cogPt, 5, YELLOW, 6,1,0);
	for(int  i = 0; i <  fingerTips.size();i++)
	{
		Point point = fingerTips.at(i);
		if(namedFingers.at(i)==FingerName::UNKNOWN)
		{
			circle(frame,point,5,RED,5,1,0);
		}
		else 
		{
			circle(frame, point, 5, PURPLE, 5, 1, 0);
		}
	}
}

void HandTrack::draw_frame()
{
	if(frame.data)
	{
		fingerTips.clear();
		imshow(window_output, frame);
	}
}
void HandTrack::drawMask()
{
	if(mask.data)
	{
		imshow(window_mask, mask);
		imshow(window_foreground, iMaskCodeBookCC);
	}
}


/**
Logs some data - it's similar to the Log from Android.
*/
void LogData(Mat frame, string text)
{
	int fontFace = FONT_HERSHEY_PLAIN;
	putText(frame, text, Point(frame.cols/15, frame.rows/15), fontFace, 1.2f, Scalar(2,0, 0), 2);
}

void LogData(Mat frame, Point location, string text)
{
	int fontFace = FONT_HERSHEY_PLAIN;
	Point pt;
	pt.x = location.x + 2;
	pt.y = location.y + 2;
	putText(frame, text, pt, fontFace, 1.2f, RED, 2);
}




